`include "defines.d"
`define ID .router_id_x(router_id_x), .router_id_y(router_id_y)
module control(
	// inputs
	clock, reset,
	dest_e, dest_s, dest_w, dest_n, dest_h,			// input queue destinations
	type_e, type_s, type_w, type_n, type_h,			// input queue types
	valid_e, valid_s, valid_w, valid_n, valid_h,		// input valids	
	om_ready_e, om_ready_s, om_ready_w, om_ready_n, om_ready_h,		// output channel readies
	
	// outputs
	q_ready_e, q_ready_s, q_ready_w, q_ready_n, q_ready_h,	// ready outputs back to input queues
	sel_e, sel_s, sel_w, sel_n, sel_h					// crossbar selector outputs
);
	parameter [2:0] router_id_x = 3'b000;
	parameter [2:0] router_id_y = 3'b000;

	input wire clock, reset;
	input wire [5:0] dest_e, dest_s, dest_w, dest_n, dest_h;			// input queue destinations
	input wire [1:0] type_e, type_s, type_w, type_n, type_h;			// input queue types
	input wire		  valid_e, valid_s, valid_w, valid_n, valid_h;	// input valids
	input wire 		  om_ready_e, om_ready_s, om_ready_w, om_ready_n, om_ready_h; // output channel readies
	
	output wire q_ready_e, q_ready_s, q_ready_w, q_ready_n, q_ready_h;	// ready outputs back to input queues
	output wire [2:0] sel_e, sel_s, sel_w, sel_n, sel_h;								// crossbar selector outputs
	
	// Route computation modules for each input queue
	wire [2:0] route_e, route_s, route_w, route_n, route_h;	
	route_computation #(`ID) RC_E (.dest(dest_e), .route(route_e));
	route_computation #(`ID) RC_S (.dest(dest_s), .route(route_s));
	route_computation #(`ID) RC_W (.dest(dest_w), .route(route_w));
	route_computation #(`ID) RC_N (.dest(dest_n), .route(route_n));
	route_computation #(`ID) RC_H (.dest(dest_h), .route(route_h));
	
	// is flit a head identifiers
	wire is_head_e = (type_e == `HEAD);
	wire is_head_s = (type_s == `HEAD);
	wire is_head_w = (type_w == `HEAD);
	wire is_head_n = (type_n == `HEAD);
	wire is_head_h = (type_h == `HEAD);
	
	// decoded held routes	
	wire [4:0] route_dec_e, route_dec_s, route_dec_w, route_dec_n, route_dec_h;	
	route_holder RH_E(.clock(clock), .reset(reset), .route_in(route_e), .is_head(is_head_e), .route_out(route_dec_e)	);
	route_holder RH_S(.clock(clock), .reset(reset), .route_in(route_s), .is_head(is_head_s), .route_out(route_dec_s)	);
	route_holder RH_W(.clock(clock), .reset(reset), .route_in(route_w), .is_head(is_head_w), .route_out(route_dec_w)	);
	route_holder RH_N(.clock(clock), .reset(reset), .route_in(route_n), .is_head(is_head_n), .route_out(route_dec_n)	);
	route_holder RH_H(.clock(clock), .reset(reset), .route_in(route_h), .is_head(is_head_h), .route_out(route_dec_h)	);
	
	// requests into router
	//arb_req_x is the route_dec_x assuming that the valid is high
	//make sure to use route_dec_x on the mux sending back to the ready on the input queue
	//that should avoid adding an extra valid ready logic loop 
	//also use arb_req_x as the input to the arbiters
	 
	wire [4:0] arb_req_e = route_dec_e & {5{valid_e}};
	wire [4:0] arb_req_s = route_dec_s & {5{valid_s}};
	wire [4:0] arb_req_w = route_dec_w & {5{valid_w}};
	wire [4:0] arb_req_n = route_dec_n & {5{valid_n}};
	wire [4:0] arb_req_h = route_dec_h & {5{valid_h}};
	//*******************************************Avtar's Work that MUST be double checked **********************************************************************************************************************
	// signals that relay if a given output module was granted to some input queue	
	wire omE_granted_to_inputE, omE_granted_to_inputS, omE_granted_to_inputW, omE_granted_to_inputN, omE_granted_to_inputH;
	wire omS_granted_to_inputE, omS_granted_to_inputS, omS_granted_to_inputW, omS_granted_to_inputN, omS_granted_to_inputH;
	wire omW_granted_to_inputE, omW_granted_to_inputS, omW_granted_to_inputW, omW_granted_to_inputN, omW_granted_to_inputH;
	wire omN_granted_to_inputE, omN_granted_to_inputS, omN_granted_to_inputW, omN_granted_to_inputN, omN_granted_to_inputH;
	wire omH_granted_to_inputE, omH_granted_to_inputS, omH_granted_to_inputW, omH_granted_to_inputN, omH_granted_to_inputH;
	
	
	arbiter arb_east (.clock(clock), .reset(reset), 
							.req_e(arb_req_e[0]), .req_s(arb_req_s[0]), .req_w(arb_req_w[0]), .req_n(arb_req_n[0]), .req_h(arb_req_h[0]),
							.type_e(type_e), .type_s(type_s), .type_w(type_w), .type_n(type_n), .type_h(type_h),
							.ready_in(om_ready_e),
							.grant_e(omE_granted_to_inputE), .grant_s(omE_granted_to_inputS), .grant_w(omE_granted_to_inputW), .grant_n(omE_granted_to_inputN), .grant_h(omE_granted_to_inputH));
							
	arbiter arb_south (.clock(clock), .reset(reset), 
							.req_e(arb_req_e[1]), .req_s(arb_req_s[1]), .req_w(arb_req_w[1]), .req_n(arb_req_n[1]), .req_h(arb_req_h[1]),
							.type_e(type_e), .type_s(type_s), .type_w(type_w), .type_n(type_n), .type_h(type_h),
							.ready_in(om_ready_s),
							.grant_e(omS_granted_to_inputE), .grant_s(omS_granted_to_inputS), .grant_w(omS_granted_to_inputW), .grant_n(omS_granted_to_inputN), .grant_h(omS_granted_to_inputH));
	arbiter arb_west (.clock(clock), .reset(reset), 
							.req_e(arb_req_e[2]), .req_s(arb_req_s[2]), .req_w(arb_req_w[2]), .req_n(arb_req_n[2]), .req_h(arb_req_h[2]),
							.type_e(type_e), .type_s(type_s), .type_w(type_w), .type_n(type_n), .type_h(type_h),
							.ready_in(om_ready_w),
							.grant_e(omW_granted_to_inputE), .grant_s(omW_granted_to_inputS), .grant_w(omW_granted_to_inputW), .grant_n(omW_granted_to_inputN), .grant_h(omW_granted_to_inputH));
							
	arbiter arb_north (.clock(clock), .reset(reset), 
							.req_e(arb_req_e[3]), .req_s(arb_req_s[3]), .req_w(arb_req_w[3]), .req_n(arb_req_n[3]), .req_h(arb_req_h[3]),
							.type_e(type_e), .type_s(type_s), .type_w(type_w), .type_n(type_n), .type_h(type_h),
							.ready_in(om_ready_n),
							.grant_e(omN_granted_to_inputE), .grant_s(omN_granted_to_inputS), .grant_w(omN_granted_to_inputW), .grant_n(omN_granted_to_inputN), .grant_h(omN_granted_to_inputH));
	
	arbiter arb_here (.clock(clock), .reset(reset), 
							.req_e(arb_req_e[4]), .req_s(arb_req_s[4]), .req_w(arb_req_w[4]), .req_n(arb_req_n[4]), .req_h(arb_req_h[4]),
							.type_e(type_e), .type_s(type_s), .type_w(type_w), .type_n(type_n), .type_h(type_h),
							.ready_in(om_ready_h),
							.grant_e(omH_granted_to_inputE), .grant_s(omH_granted_to_inputS), .grant_w(omH_granted_to_inputW), .grant_n(omH_granted_to_inputN), .grant_h(omH_granted_to_inputH));

	encodeSelects eastEncode (.eastSig(omE_granted_to_inputE), .southSig(omE_granted_to_inputS), .westSig(omE_granted_to_inputW), .northSig(omE_granted_to_inputN), .hereSig(omE_granted_to_inputH), .SelectSignal(sel_e));
	encodeSelects southEncode (.eastSig(omS_granted_to_inputE), .southSig(omS_granted_to_inputS), .westSig(omS_granted_to_inputW), .northSig(omS_granted_to_inputN), .hereSig(omS_granted_to_inputH), .SelectSignal(sel_s));
	encodeSelects westEncode (.eastSig(omW_granted_to_inputE), .southSig(omW_granted_to_inputS), .westSig(omW_granted_to_inputW), .northSig(omW_granted_to_inputN), .hereSig(omW_granted_to_inputH), .SelectSignal(sel_w));
	encodeSelects northEncode (.eastSig(omN_granted_to_inputE), .southSig(omN_granted_to_inputS), .westSig(omN_granted_to_inputW), .northSig(omN_granted_to_inputN), .hereSig(omN_granted_to_inputH), .SelectSignal(sel_n));
	encodeSelects hereEncode (.eastSig(omH_granted_to_inputE), .southSig(omH_granted_to_inputS), .westSig(omH_granted_to_inputW), .northSig(omH_granted_to_inputN), .hereSig(omH_granted_to_inputH), .SelectSignal(sel_h));
	
	//for q_ready_e the logic is as follows
	//its requested OM -> is it ready?
	//if its ready, did it win arbitration for that OM?
	
	wire east_q_requested_om_ready_bit, south_q_requested_om_ready_bit, west_q_requested_om_ready_bit, north_q_requested_om_ready_bit, here_q_requested_om_ready_bit ;
	
	assign east_q_requested_om_ready_bit = (route_dec_e == `EAST_DEC) ? om_ready_e : 
												      (route_dec_e == `SOUTH_DEC) ? om_ready_s : 
												      (route_dec_e == `WEST_DEC) ? om_ready_w :     
												      (route_dec_e == `NORTH_DEC) ? om_ready_n : 
												      (route_dec_e == `HERE_DEC) ? om_ready_h :
												      0;
	assign south_q_requested_om_ready_bit = (route_dec_s == `EAST_DEC) ? om_ready_e : 
												      (route_dec_s == `SOUTH_DEC) ? om_ready_s : 
												      (route_dec_s == `WEST_DEC) ? om_ready_w :     
												      (route_dec_s == `NORTH_DEC) ? om_ready_n : 
												      (route_dec_s == `HERE_DEC) ? om_ready_h :
												      0;
	assign west_q_requested_om_ready_bit = (route_dec_w == `EAST_DEC) ? om_ready_e : 
												      (route_dec_w == `SOUTH_DEC) ? om_ready_s : 
												      (route_dec_w == `WEST_DEC) ? om_ready_w :     
												      (route_dec_w == `NORTH_DEC) ? om_ready_n : 
												      (route_dec_w == `HERE_DEC) ? om_ready_h :
												      0;		
	assign north_q_requested_om_ready_bit = (route_dec_n == `EAST_DEC) ? om_ready_e : 
												      (route_dec_n == `SOUTH_DEC) ? om_ready_s : 
												      (route_dec_n == `WEST_DEC) ? om_ready_w :     
												      (route_dec_n == `NORTH_DEC) ? om_ready_n : 
												      (route_dec_n == `HERE_DEC) ? om_ready_h :
												      0;				
	assign here_q_requested_om_ready_bit = (route_dec_h == `EAST_DEC) ? om_ready_e : 
												      (route_dec_h == `SOUTH_DEC) ? om_ready_s : 
												      (route_dec_h == `WEST_DEC) ? om_ready_w :     
												      (route_dec_h == `NORTH_DEC) ? om_ready_n : 
												      (route_dec_h == `HERE_DEC) ? om_ready_h :
												      0;												    									      
												      
												      
												      
												      
												      									    
	assign q_ready_e = (east_q_requested_om_ready_bit) & (omE_granted_to_inputE | omS_granted_to_inputE | omW_granted_to_inputE | omN_granted_to_inputE | omH_granted_to_inputE);
	assign q_ready_s = (south_q_requested_om_ready_bit) & (omE_granted_to_inputS | omS_granted_to_inputS | omW_granted_to_inputS | omN_granted_to_inputS | omH_granted_to_inputS);
	assign q_ready_w = (west_q_requested_om_ready_bit) & (omE_granted_to_inputW | omS_granted_to_inputW | omW_granted_to_inputW | omN_granted_to_inputW | omH_granted_to_inputW);
	assign q_ready_n = (north_q_requested_om_ready_bit) & (omE_granted_to_inputN | omS_granted_to_inputN | omW_granted_to_inputN | omN_granted_to_inputN | omH_granted_to_inputN);
	assign q_ready_h = (here_q_requested_om_ready_bit) & (omE_granted_to_inputH | omS_granted_to_inputH | omW_granted_to_inputH | omN_granted_to_inputH | omH_granted_to_inputH);
                     

endmodule


module encodeSelects (eastSig, southSig, westSig, northSig, hereSig, SelectSignal);
	input wire eastSig, southSig, westSig, northSig, hereSig;
	output wire [2:0] SelectSignal;
	
	assign SelectSignal = (eastSig) ? `EAST :
	                      (southSig) ? `SOUTH :
	                      (westSig) ? `WEST:
	                      (northSig) ? `NORTH :
	                      (hereSig) ? `HERE :
	                      `NO_ROUTE;
endmodule
//******************************************************** END OF WORK WHICH NEEDS TO BE DOUBLE CHECKED ***********************************************************************************

module route_holder(clock, reset, route_in, is_head, route_out);
	// holds a requested route for the durration of the packet, i.e., is only updated on head flits
	// also decodes output	
	input wire clock, reset;
	input wire [2:0] route_in;
	input wire  is_head;
	output wire [4:0] route_out;	
	
	reg [2:0] route_latched; // for internal storage of the route to be held

	always @(posedge clock or negedge reset) begin
		if(!reset) route_latched <= `NO_ROUTE;
		else begin
			if(is_head) route_latched <= `TICK route_in;
			else route_latched <= `TICK route_latched; 
		end
	end
	wire [2:0] route_bypassed = (is_head) ? route_in : route_latched;	
	decoder_3_5 DEC(.e_bus(route_bypassed), .d_bus(route_out));

endmodule // route_holder

module decoder_3_5 (e_bus, d_bus);
	input wire [2:0] e_bus;
	output wire [4:0] d_bus;
	
	assign d_bus = (e_bus == `EAST)	? 5'b00001 :
						(e_bus == `SOUTH)	? 5'b00010 :
						(e_bus == `WEST)	? 5'b00100 :
						(e_bus == `NORTH)	? 5'b01000 :
						(e_bus == `HERE)	? 5'b10000 :
						5'b00000;

endmodule // decoder_3_5

module route_computation (dest, route);
       // one RC module per input port, only care about its output in the case that the flit is a header
       //output port is one of the following:
       // 1: East
       // 2: South
       // 3: west
   	 // 4: north
       // 5: here
       //inputBits [2:0] -> x bits
       // inputBits [5:3] -> ybits
       // router 0 is all the way at the north and west
	parameter [2:0] router_id_x = 3'b000;
	parameter [2:0] router_id_y = 3'b000;
       
       input wire [5:0] dest;
       wire [2:0] destination_x = dest[2:0];
       wire [2:0] destination_y = dest[5:0];
       
       output [2:0] route;
       assign route = 	(destination_x > router_id_x) ? `EAST 	:	// Need to keep going east along x dimension
       						(destination_x < router_id_x) ? `WEST 	:	// Need to keep going west along x dimension
       						// otherwise, we're at the right X dimension, need to turn into Y dimension
       						(destination_y > router_id_y) ? `SOUTH	: 	// Need to go south
       						(destination_y < router_id_y) ? `NORTH : 	// Need to go north
       						`HERE;												// At the target router

endmodule
